package com.red.wine.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import com.red.wine.WineApp;

public class CacheStore implements ICache<String> {

    private static final int EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 4;// 4 days
    private static final int CLEANING_INTERVAL = 1000 * 60 * 10;// 10 mins
    private static final int MAX_CACHE_SIZE = 1024 * 4;// 4 MB
    private static final int MAX_CACHE_CAPACITY = 200;

    private static final CacheStore instance = new CacheStore();

    public static CacheStore getInstance() {
        return instance;
    }

    private static void listFiles(Collection<File> files, File directory) {
        File[] found = directory.listFiles();
        if (found != null) {
            for (File file : found) {
                if (file.isDirectory()) {
                    listFiles(files, file);
                    // select part,escape from crash
                    if (files.size() >= MAX_CACHE_CAPACITY) {
                        break;
                    }
                    files.add(file);
                } else {
                    // select part,escape from crash
                    if (files.size() >= MAX_CACHE_CAPACITY) {
                        break;
                    }
                    files.add(file);
                }
            }
        }
    }

    private long getCurrentCacheSize(File directory) {
        File[] found = directory.listFiles();
        long size = 0;
        if (found != null) {
            for (File file : found) {
                if (file.isDirectory()) {
                    size += getCurrentCacheSize(file);
                } else {
                    size += file.length();
                }
            }
        }
        return size;
    }

    private String filePath;

    private CacheStore() {
        filePath = WineApp.getInstance().getFilePath();
        final File cacheRoot = new File(filePath);
        Thread cacheMaintenanceThread = new Thread() {
            @Override
            public void run() {
                List<File> currentFiles = new ArrayList<File>();

                while (true) {
                    if (cacheRoot.equals("")) {
                        if (getCurrentCacheSize(cacheRoot) >= MAX_CACHE_SIZE) {
                            listFiles(currentFiles, cacheRoot);
                            for (File file : currentFiles) {
                                if (!file.getName().contains(".apk") && !file.getName().contains(".tmp")) {
                                    file.delete();
                                }
                            }
                        }
                    }

                    currentFiles.clear();
                    listFiles(currentFiles, cacheRoot);

                    //
                    // Check for expired files When cache files in cache dir
                    // exceeds max capacity.
                    //
                    if (currentFiles != null
                            && currentFiles.size() >= MAX_CACHE_CAPACITY) {
                        Date now = new Date();
                        Iterator<File> it = currentFiles.iterator();
                        while (it.hasNext()) {
                            File file = it.next();
                            if (file.isDirectory()) {// empty dir deleted
                                file.delete();// if successfully, then it is
                                                // empty dir
                            } else if (now.getTime() - file.lastModified() >= EXPIRATION_TIME) {
                                if (!file.getName().contains(".apk") && !file.getName().contains(".tmp")) {
                                    if (!file.delete()) {
                                        file.deleteOnExit();
                                    }
                                }
                            }
                        }
                    }
                    try {
                        Thread.sleep(CLEANING_INTERVAL);
                    } catch (InterruptedException ex) {
                        // Do nothing, continue.
                    }
                }
            }
        };
        cacheMaintenanceThread.setDaemon(true);
        cacheMaintenanceThread.start();

    }

    private String getHashFilepath(String url) {
        if (StringUtil.isEmptyOrNull(url)) {
            return null;
        }
        String hashString = String.valueOf(Math.abs(url.hashCode()) % 1000);
        return filePath
                + "/"
                + hashString
                + "/"
                + url.replace("//", "").replace(":", "").replace("/", "")
                        .replace(".", "");
    }

    private String getHashDirpath(String url) {
        if (StringUtil.isEmptyOrNull(url)) {
            return null;
        }
        return String.valueOf(Math.abs(url.hashCode()) % 1000);
    }

    private String getFileName(String url) {
        if (StringUtil.isEmptyOrNull(url)) {
            return null;
        }
        return url.replace("//", "").replace(":", "").replace("/", "")
                .replace(".", "");
    }

    public Object get(String key) {
        File file = new File(getHashFilepath(key));
        if (file.exists()) {
            FileInputStream stream = null;
            try {
                stream = new FileInputStream(file);
                return stream;
            } catch (FileNotFoundException ex) {
                // Will not happen.
            } finally {
                if (stream != null) {
                    try {
                        stream.close();
                    } catch (IOException ex) {
                        // Do nothing.
                    }
                }
            }
        }
        return null;
    }

    public Object get(String key, Object defaultValue) {
        Object item = get(key);
        if (item == null) {
            return defaultValue;
        }
        return item;
    }

    public synchronized void put(String key, Object value) {
        File dir = new File(filePath + "/" + getHashDirpath(key));
        dir.mkdirs();
        File file = new File(dir, getFileName(key));
        InputStream inputStream = (InputStream) value;
        FileOutputStream stream = null;
        try {
            stream = new FileOutputStream(file);
            byte[] buffer = new byte[8 * 1024];
            int read;
            while ((read = inputStream.read(buffer)) > -1) {
                stream.write(buffer, 0, read);
            }
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
            // Will not happen.
        } catch (IOException ex) {
            // In case this happens, just ignore it.
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException ex) {
                    // Do nothing.
                }
            }
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException ex) {
                    // Do nothing.
                }
            }
        }
    }

    public InputStream getReadStream(String key) {
    	String filePath = getHashFilepath(key);
    	if (filePath == null){
    		return null;
    	}
        File file = new File(filePath);
        if (file.exists()) {
            try {
                file.setLastModified(System.currentTimeMillis());
                return new FileInputStream(file);
            }
            catch (IllegalArgumentException	ex){//happen once, why?
            	// Return null in this case.
            }
            catch (FileNotFoundException ex) {
                // Return null in this case.
            }
        }

        return null;
    }

    public OutputStream getWriteStream(String key) {
        File file = new File(getHashFilepath(key));
        try {
            file.createNewFile();
            return new FileOutputStream(file);
        } catch (IOException ex) {
            // Will not happen.
        }
        return null;
    }

    public OutputStream getWriteStream(String key, int length) {
        File file = new File(getHashFilepath(key));
        try {
            file.createNewFile();
            return new FileOutputStream(file);
        } catch (IOException ex) {
            // Will not happen.
        }
        return null;
    }

    public void remove(String key) {
        File file = new File(filePath + "/" + getFileName(key));
        if (!file.delete()) {
            file.deleteOnExit();
        }
    }

    public boolean exists(String key) {
        return new File(filePath + "/" + getFileName(key)).exists();
    }

}
